<!DOCTYPE html>
<html>
  <head>
    <title>
      Test AnalyserNode getFloatTimeDomainData
    </title>
    <script src="../../resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit-util.js"></script>
    <script src="../resources/audit.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      // Use a power of two to eliminate any round-off in the computation of the
      // times for context.suspend().
      let sampleRate = 32768;

      // The largest FFT size for the analyser node is 32768.  We want to render
      // longer than this so that we have at least one complete buffer of data
      // of 32768 samples.
      let renderFrames = 2 * 32768;
      let renderDuration = renderFrames / sampleRate;

      let audit = Audit.createTaskRunner();

      // Test that getFloatTimeDomainData handles short and long vectors
      // correctly.
      audit.define('short and long vector', (task, should) => {
        let fftSize = 32;
        let graphInfo = createGraph(fftSize);
        let context = graphInfo.context;
        let analyser = graphInfo.analyser;
        let signalBuffer = graphInfo.signalBuffer;
        let signal = signalBuffer.getChannelData(0);

        let success = true;
        let sampleFrame = 128;

        context.suspend(sampleFrame / sampleRate)
            .then(function() {
              let shortData = new Float32Array(8);
              // Initialize the array to Infinity to represent uninitialize
              // data.
              shortData.fill(Infinity);

              analyser.getFloatTimeDomainData(shortData);

              // The short array should be filled with the expected data, with
              // no errors thrown.

              let expected =
                  signal.subarray(sampleFrame - fftSize, sampleFrame);
              should(shortData, shortData.length + '-element time domain data')
                  .beEqualToArray(expected.subarray(0, shortData.length));

              let longData = new Float32Array(2 * fftSize);
              // Initialize the array to Infinity to represent uninitialize
              // data.
              longData.fill(Infinity);

              analyser.getFloatTimeDomainData(longData);

              // The long array should filled with the expected data but the
              // extra elements should be untouched.
              should(
                  longData.subarray(0, fftSize),
                  'longData.subarray(0, ' + fftSize + ')')
                  .beEqualToArray(expected);

              should(
                  longData.subarray(fftSize),
                  'Unfilled elements longData.subarray(' + fftSize + ')')
                  .beConstantValueOf(Infinity);
            })
            .then(context.resume.bind(context));

        context.startRendering().then(() => task.done());
      });

      let success = true;

      // Generate tests for all valid FFT sizes for an AnalyserNode.
      for (let k = 5; k < 16; ++k) {
        let fftSize = Math.pow(2, k);
        (function(n) {
          // We grab a sample at (roughly) half the rendering duration.
          audit.define('fftSize ' + n, (task, should) => {
            runTest(n, renderDuration / 2, should).then(() => task.done());
          });
        })(fftSize);
      }

      // Special case for a large size, but the sampling point is early.  The
      // initial part of the buffer should be filled with zeroes.

      audit.define('initial zeroes', (task, should) => {
        // Somewhat arbitrary size for the analyser.  It should be greater than
        // one rendering quantum.
        let fftSize = 2048;
        let graphInfo = createGraph(fftSize);
        let context = graphInfo.context;
        let analyser = graphInfo.analyser;
        let signalBuffer = graphInfo.signalBuffer;

        let data = new Float32Array(fftSize);

        success = true;
        // Suspend every rendering quantum and examine the analyser data.
        for (let k = 128; k <= fftSize; k += 128) {
          context.suspend(k / sampleRate)
              .then(function() {
                analyser.getFloatTimeDomainData(data);
                let sampleFrame = context.currentTime * sampleRate;

                // Verify that the last k frames are not zero, but the first
                // fftSize - k frames are.
                let prefix =
                    'At frame ' + (sampleFrame - 1) + ': data.subarray';
                if (sampleFrame < fftSize) {
                  should(
                      data.subarray(0, fftSize - sampleFrame),
                      prefix + '(0, ' + (fftSize - sampleFrame) + ')')
                          .beConstantValueOf(0) &&
                      success;
                }

                let signal = signalBuffer.getChannelData(0);
                should(
                    data.subarray(fftSize - sampleFrame, fftSize),
                    prefix + '(' + (fftSize - sampleFrame) + ', ' + fftSize +
                        ')')
                        .beEqualToArray(signal.subarray(0, sampleFrame)) &&
                    success;
              })
              .then(context.resume.bind(context));
        }

        context.startRendering().then(() => task.done());
      });

      audit.run();

      // Run test of an AnalyserNode with fftSize of |fftSize|, and with the
      // data from the node being requested at time |sampletime|.  The result
      // from the analyser node is compared against the expected data.  The
      // result of startRendering() is returned.
      function runTest(fftSize, sampleTime, should) {
        let graphInfo = createGraph(fftSize);
        let context = graphInfo.context;
        let analyser = graphInfo.analyser;
        let signalBuffer = graphInfo.signalBuffer;

        // Grab the data at the requested time.
        context.suspend(sampleTime)
            .then(function() {
              let lastFrame = Math.floor(context.currentTime * sampleRate);

              // Grab the time domain data from the analyzer and compare against
              // the expected result.
              let actualFloatData = new Float32Array(fftSize);
              analyser.getFloatTimeDomainData(actualFloatData);

              // Compare against the expected result.
              let signal = signalBuffer.getChannelData(0);
              let message =
                  actualFloatData.length + '-point analyser time domain data';
              should(actualFloatData, message)
                      .beEqualToArray(signal.subarray(
                          lastFrame - actualFloatData.length, lastFrame)) &&
                  success;
            })
            .then(context.resume.bind(context));

        return context.startRendering();
      }

      // Create the audio graph with an AnalyserNode with fftSize |fftSize|.  A
      // simple integer-valued linear ramp is the source so we can easily verify
      // the results.  A dictionary consisting of the context, the analyser
      // node, and the signal is returned.
      function createGraph(fftSize) {
        let context = new OfflineAudioContext(1, renderFrames, sampleRate);

        let src = context.createBufferSource();

        // Use a simple linear ramp as the source.  For simplicity of inspecting
        // results, the ramp starts at 1 with an increment of 1.
        let signalBuffer =
            context.createBuffer(1, renderFrames, context.sampleRate);
        let data = signalBuffer.getChannelData(0);
        for (let k = 0; k < data.length; ++k) {
          data[k] = k + 1;
        }

        src.buffer = signalBuffer;

        let analyser = context.createAnalyser();
        analyser.fftSize = fftSize;

        src.connect(analyser);
        analyser.connect(context.destination);
        src.start();

        return {
          context: context,
          analyser: analyser,
          signalBuffer: signalBuffer
        };
      }
    </script>
  </body>
</html>
